/*
 * Canvas Game Engine - Map Module
 *
 * version: 0.1
 *
 * author: Fabio Rotondo <fabio@rotondo.it>
 *
 * requires: image_handler
 */

// Creates a new CGE Map
// Parameters:
//
// 	- mh: 	CGEMapHandler instance
// 	- img_url:  Image URL
// 	- bw, bh: block width and height
//	- name: Map name
function CGEMap ( mh, img_url, bw, bh, name )
{
	var self = this;

	this.mh  = mh;

	this.bw = bw;		// Block width
	this.bh = bh;		// Block height

	this.name = name;	// Map name

	this.view_x = 0;	// View offset X
	this.view_y = 0;	// View offset Y
	this.view_w = 0;	// View width and height
	this.view_h = 0;

	this.block_offset = 0;

	// Pixel position of the first left topmost point of the View Window
	this.win_x = bw;
	this.win_y = bh;

	//Position x,y on map (in pixels)
	this.abs_x = 0;
	this.abs_y = 0;

	this._id = "map-" + name;

	this.img = CGE.image_handler.add ( img_url, this._id );

	// Create a local canvas
	this._c = [
		CGE.canvas_handler.add ( this._id + ":c1", 1, 1 ),
		CGE.canvas_handler.add ( this._id + ":c2", 1, 1 )
	];

	this._curr_canvas = 0;

	this._canvas = this._c [ this._curr_canvas ];
}

CGEMap.prototype.set_view_offset = function ( x, y )
{
	this.view_x = x - 1;
	this.view_y = y;
};

// This method redraws the complete map on canvas
CGEMap.prototype.redraw = function ()
{
	var bw, bh;

	bw = this.bw;
	bh = this.bh;

	this._calc_sizes ();

	// Resize the canvas with a "buffer" of 1 column and row for each side
	this._c [ 0 ].set_size ( ( this.view_w + 2 ) * bw, ( this.view_h + 2 ) * bh );
	this._c [ 1 ].set_size ( ( this.view_w + 2 ) * bw, ( this.view_h + 2 ) * bh );

	// Render the whole visible map inside the canvas
	var x, y, block, sx, sy;
	var pos_x, pos_y, row, img, ctxt;
	var stop_x, stop_y;

	pos_y = this.view_y;
	img = this.img.img;
	ctxt = this._canvas.ctxt;
	bpr = this._blocks_per_row;

	stop_x = this.view_w + 2;
	stop_y = this.view_h + 2;

	// Clear the buffer canvas
	this._canvas.clear ();

	// Render the blocks
	for ( y = 0; y < stop_y; y ++ )
	{
		pos_y = y + this.view_y;

		if ( pos_y >  this._map_h ) continue;

		// get the right row
		row = this._map [ pos_y ];
		if ( ! row ) continue;

		for ( x = 0; x < stop_x; x ++ )
		{
			pos_x = this.view_x + x;
			if ( pos_x > this._map_w ) continue;

			block = row [ pos_x ];
			if ( block == 0 ) continue;

			block -= this.block_offset;

			sx = ( block % bpr );
			sy = Math.floor ( block / bpr );

			ctxt.drawImage ( img, sx * bw, sy * bh, bw, bh, x * bw, y * bh, bw, bh );
		}
	}

	this._c [ 1 ].ctxt.drawImage ( this._c [ 0 ].canvas, 0, 0 );
};

CGEMap.prototype._calc_sizes = function ()
{
	var w = CGE.canvas.canvas.width;
	var h = CGE.canvas.canvas.height;

	this.view_w = Math.ceil ( w / this.bw );
	this.view_h = Math.ceil ( h / this.bh );

	this._blocks_per_row = Math.floor ( this.img.w / this.bw );

	// console.debug ( "W: %s - h: %s - img: w: %s - h: %s - size: %s - %s - blocks per row: %s", w, h, this.img.w, this.img.h, this.view_w, this.view_h, this._blocks_per_row );
};

CGEMap.prototype.set_map = function ( map_descr )
{
	this._map = map_descr;

	this._map_w = map_descr [ 0 ].length;
	this._map_h = map_descr.length;
};

CGEMap.prototype.blit = function ()
{
	CGE.canvas.ctxt.drawImage ( this._canvas.canvas, - this.win_x,  - this.win_y );
};

CGEMap.prototype.add = function ( delta_x, delta_y )
{
	if ( ! delta_x && ! delta_y ) return;

	this.abs_x += delta_x;
	this.abs_y += delta_y;

	var c = this._c [ this._curr_canvas ];
	var sx = 0, sy = 0, w = c.w, h = c.h, dx = 0, dy = 0;
	var ops = [];
	var self = this;

	if ( delta_x )
	{
		this.win_x += delta_x;

		if ( this.win_x > ( this.bw * 2 ) )
		{
			sx = this.bw;
			w  = c.w - this.bw;

			ops.push ( function () { self.win_x = self.bw; self.view_x ++; self._draw_right_column (); } );

		} else if ( this.win_x < 0 ) {
			dx = this.bw;
			w  = c.w - this.bw;

			ops.push ( function () { self.win_x = self.bw; self.view_x --; self._draw_left_column (); } );
		}
	}

	if ( delta_y )
	{
		this.win_y += delta_y;

		if ( this.win_y > ( this.bh * 2 ) ) 
		{
			sy = this.bh;
			h  = c.h - this.bh;

			ops.push ( function () { self.win_y = self.bh; self.view_y ++; self._draw_bottom_row (); } );
		} else  if ( this.win_y < 0 ) {
			dy = this.bh;
			h  = c.h - this.bh;

			ops.push ( function () { self.win_y = self.bh; self.view_y --; self._draw_top_row (); } );
		}
	}

	if ( ops.length )
	{
		this._copy ( sx, sy, w, h, dx, dy );
		this._switch_canvas ();

		var t, l = ops.length;

		for ( t = 0; t < l; t ++ ) ops [ t ] ();
	}
};

CGEMap.prototype._switch_canvas = function ()
{
	this._curr_canvas = 1 - this._curr_canvas;
	this._canvas = this._c [ this._curr_canvas ];
};

CGEMap.prototype._draw_right_column = function ()
{
	this._draw_column ( this.view_w + this.view_x + 1, this.view_w + 1 );
};

CGEMap.prototype._draw_left_column = function ()
{
	this._draw_column ( this.view_x, 0 );
};

CGEMap.prototype._draw_column = function ( x, offset )
{
	var ctxt = this._canvas.ctxt;
	var y;
	var bw = this.bw, bh = this.bh, bpr, row;
	var img = this.img.img;

	bpr = this._blocks_per_row;


	// Leave "-1" because pos is incremented on first loop
	var pos = -1;
	var pos_dest = offset * bw;

	// console.debug ( "COLUMN POS DEST: %s", pos_dest );

	for ( y = this.view_y; y < this.view_y + this.view_h + 2; y ++ )
	{
		++ pos;

		row = this._map [ y ];

		if ( ! row ) continue;

		block = row [ x ];

		if ( ! block ) continue;

		block -= this.block_offset;

		sx = ( block % bpr );
		sy = Math.floor ( block / bpr );

		// console.debug ( "COL: sx: %s sy: %s w: %s h: %s pos_dest: %s pos: %s", sx, sy, bw, bh, pos_dest, pos );
		ctxt.drawImage ( img, sx * bw, sy * bh, bw, bh, pos_dest, pos * bh, bw, bh );
		// ctxt.drawImage ( img, sx * bw, sy * bh, bw, bh, ( x - 1 ) * bw, y * bh, bw, bh );
	}
};

CGEMap.prototype._copy = function ( sx, sy, w, h, dx, dy )
{
	var src = this._c [ this._curr_canvas ];
	var dest = this._c [ 1 - this._curr_canvas ];

	// console.debug ( "COPY: sx: %s sy: %s w: %s h: %s pos_dest: %s pos: %s", sx, sy, w, h, dx, dy );

	dest.clear ();
	dest.ctxt.drawImage ( src.canvas, sx, sy, w, h, dx, dy, w, h );

};


CGEMap.prototype._draw_top_row = function ()
{
	this._draw_row ( this.view_y, 0 );
};

CGEMap.prototype._draw_bottom_row = function ()
{
	this._draw_row ( this.view_y + this.view_h + 1, this.view_h + 1 );
};

CGEMap.prototype._draw_row = function ( y, offset )
{
	var ctxt = this._canvas.ctxt;
	var x, pos;
	var bw = this.bw, bh = this.bh, bpr, row;
	var img = this.img.img;
	var start_x, end_x;

	bpr = this._blocks_per_row;

	var pos = 0;
	var pos_dest = offset * bh;

	row = this._map [ y ];

	start_x = this.view_x;
	end_x   = start_x + this.view_w + 1;

	// Leave "-1" because pos is incremented on first loop
	pos = -1;

	for ( x = start_x; x < end_x + 1; x ++ )
	{
		++ pos;

		if ( ! row ) continue;

		block = row [ x ];

		if ( ! block ) continue;

		block -= this.block_offset;

		sx = ( block % bpr );
		sy = Math.floor ( block / bpr );

		ctxt.drawImage ( img, sx * bw, sy * bh, bw, bh, pos * bw, pos_dest, bw, bh );
	}
};


function CGEMapHandler ()
{
	this.maps = {};
}

CGEMapHandler.prototype.add = function ( url, bw, bh, name )
{
	var m;

	if ( ! name ) name = url;

	// You cannot have two images with the same name (identifier)
	if ( this.maps [ name ] ) return this.maps [ name ];

	m = new CGEMap ( this, url, bw, bh, name );
	this.maps [ name ] = m;

	return m;
};

CGEMapHandler.prototype.get = function ( name )
{
	return this.maps [ name ];
};

CGEMapHandler.prototype.ready = function () { return true; };

// Register the ImageHandler to the system
CGE.register ( "map_handler", new CGEMapHandler () );
